ποΈGitΠ―ΡΠ°ποΈ
Commit 480334a0ad4c06e8ea879131557040647375499e
Parents : 9c864a8
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-07-24T09:24:02-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-07-24T09:24:02-05:00
chore(ci): drop setup job, rebalance test shards, move coverage to main (#6405)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Changes
4 files changed, 84 insertions(+), 100 deletions(-)
Diff
diff --git a/.github/workflows/main-check.yml b/.github/workflows/main-check.yml
index cdffe86a84..b77a06604b 100644
--- a/.github/workflows/main-check.yml
+++ b/.github/workflows/main-check.yml
@@ -15,19 +15,26 @@ concurrency:
cancel-in-progress: true
jobs:
- # Every commit on main arrives via the merge queue, which already ran lint, tests,
- # screenshot validation, and rb-check on this exact merge commit. Re-running them here
- # would be pure duplication β this workflow builds the debug APKs for the snapshot
- # release below (run_lint: false also skips screenshot-check and rb-check) plus the
- # desktop distributables. Desktop packaging runs here post-merge rather than in the
- # merge queue: :desktopApp:test in the queue's shard-app already covers compilation,
- # and the 4-OS matrix (macos/windows queue times) would slow every merge.
+ # Every commit on main arrives via the merge queue, which already ran lint,
+ # screenshot validation, rb-check, and the (coverage-free) test shards on this
+ # exact merge commit. This workflow:
+ # - re-runs the test shards WITH coverage: Kover instrumentation lives here,
+ # off the queue's critical path, and this is the sole source of Codecov
+ # mainline coverage (PRs and the queue both skip it)
+ # - builds the debug APKs for the snapshot release below with -SNAPSHOT
+ # naming (the queue skips run_android_build; the PR already assembled them)
+ # - builds the desktop distributables. Desktop packaging runs here post-merge
+ # rather than in the merge queue: :desktopApp:test in the queue's shard-app
+ # already covers compilation, and the 4-OS matrix (macos/windows queue
+ # times) would slow every merge.
+ # run_lint: false skips lint, screenshot-check, and rb-check (queue-verified).
validate-and-build:
if: github.repository == 'meshtastic/Meshtastic-Android'
uses: ./.github/workflows/reusable-check.yml
with:
run_lint: false
- run_unit_tests: false
+ run_unit_tests: true
+ run_coverage: true
run_desktop_builds: true
upload_artifacts: true
secrets: inherit
diff --git a/.github/workflows/merge-queue.yml b/.github/workflows/merge-queue.yml
index e0e4bb7571..44e671f158 100644
--- a/.github/workflows/merge-queue.yml
+++ b/.github/workflows/merge-queue.yml
@@ -79,6 +79,15 @@ jobs:
with:
run_lint: true
run_unit_tests: true
+ # Coverage is produced by main-check on the identical merge commit right
+ # after the queue merges it β keeping Kover instrumentation and report
+ # generation out of the queue's critical-path test shards.
+ run_coverage: false
+ # The PR already assembled these APKs and main-check rebuilds them (with
+ # -SNAPSHOT naming) for the snapshot release; a merge-combination
+ # packaging break would surface there minutes later. Skipping saves a
+ # runner slot per queue entry.
+ run_android_build: false
upload_artifacts: false
secrets: inherit
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 55108cd4db..7bcd0a291e 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -12,7 +12,10 @@ concurrency:
cancel-in-progress: true
jobs:
- # 1. CHANGE DETECTION: Prevents unnecessary builds
+ # 1. CHANGE DETECTION: Prevents unnecessary builds. Also verifies the path
+ # filter below stays aligned with the module roots in settings.gradle.kts
+ # (folded into this job rather than run standalone: runner-pool slots, not
+ # compute, are the scarce resource during queue bursts).
check-changes:
if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' )
runs-on: ubuntu-24.04-arm
@@ -51,14 +54,6 @@ jobs:
- 'gradlew.bat'
- 'settings.gradle.kts'
- 'test.gradle.kts'
-
- # 1b. FILTER DRIFT CHECK: Ensures check-changes stays aligned with module roots
- verify-check-changes-filter:
- if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' )
- runs-on: ubuntu-24.04-arm
- timeout-minutes: 10
- steps:
- - uses: actions/checkout@v7.0.1
- name: Verify module roots are represented in check-changes filter
run: |
python3 - <<'PY'
@@ -81,10 +76,12 @@ jobs:
for path in re.findall(r"-\s*'([^']+/\*\*)'", workflow)
}
- actual_module_roots = filter_paths & expected_roots
+ # Filter roots that are intentionally not Gradle module roots
+ # (CI/workflow implementation + shared build infrastructure).
+ allowed_infra_roots = {'.github', 'build-logic', 'config', 'gradle'}
- missing = sorted(expected_roots - actual_module_roots)
- unexpected = sorted(actual_module_roots - expected_roots)
+ missing = sorted(expected_roots - filter_paths)
+ unexpected = sorted(filter_paths - expected_roots - allowed_infra_roots)
if missing or unexpected:
print('check-changes filter drift detected:')
@@ -153,13 +150,14 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
permissions: {}
- needs: [check-changes, verify-check-changes-filter, check-metadata, validate-and-build]
+ needs: [check-changes, check-metadata, validate-and-build]
if: always()
steps:
- name: Check Workflow Status
run: |
- if [[ "${{ needs.verify-check-changes-filter.result }}" == "failure" || "${{ needs.verify-check-changes-filter.result }}" == "cancelled" ]]; then
- echo "::error::check-changes filter verification failed"
+ # skipped is fine (bot branches); failure also covers the filter-drift step
+ if [[ "${{ needs.check-changes.result }}" == "failure" || "${{ needs.check-changes.result }}" == "cancelled" ]]; then
+ echo "::error::Change detection or filter drift check failed"
exit 1
fi
diff --git a/.github/workflows/reusable-check.yml b/.github/workflows/reusable-check.yml
index 557f833a97..996506d516 100644
--- a/.github/workflows/reusable-check.yml
+++ b/.github/workflows/reusable-check.yml
@@ -9,6 +9,9 @@ on:
run_unit_tests:
type: boolean
default: true
+ run_android_build:
+ type: boolean
+ default: true
run_coverage:
type: boolean
default: true
@@ -47,47 +50,19 @@ env:
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
- # Fallback VERSION_CODE for jobs that don't consume the setup output.
- VERSION_CODE: ${{ github.run_number }}
+ # Merge-queue and main-branch runs write to the Gradle caches; every other
+ # context (PRs, release tags) is a read-only consumer.
+ GRADLE_CACHE_READ_ONLY: ${{ (github.ref == 'refs/heads/main' || github.event_name == 'merge_group') && 'false' || 'true' }}
jobs:
- # ββ Fast Setup (version code + cache config) ββββββββββββββββββββββββ
- # Lightweight job that unblocks lint, tests, and builds in parallel.
- setup:
- runs-on: ubuntu-24.04-arm
- permissions:
- contents: read
- timeout-minutes: 5
- outputs:
- cache_read_only: ${{ steps.cache_config.outputs.cache_read_only }}
- version_code: ${{ steps.version_code.outputs.version_code }}
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v7.0.1
- with:
- fetch-depth: 0
- filter: 'blob:none'
- submodules: false
-
- - name: Determine cache read-only setting
- id: cache_config
- shell: bash
- run: |
- if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.event_name }}" == "merge_group" ]] || [[ "${{ github.ref }}" == gh-readonly-queue/* ]]; then
- echo "cache_read_only=false" >> "$GITHUB_OUTPUT"
- else
- echo "cache_read_only=true" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Calculate version code from git commit count
- id: version_code
- shell: bash
- run: |
- COMMIT_COUNT=$(git rev-list --count HEAD)
- OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2 || echo 0)
- VERSION_CODE=$((COMMIT_COUNT + OFFSET))
- echo "version_code=$VERSION_CODE" >> "$GITHUB_OUTPUT"
+ # There is deliberately no fan-in "setup" job here. Every heavy job used to
+ # `needs:` one, which serialized TWO runner-queue waits per run β under pool
+ # congestion the setup job alone sat queued for ~10 minutes before the real
+ # jobs could even enter the queue. Its two outputs are now sourced without a
+ # job: cache writability is the pure expression above, and the versionCode is
+ # derived by the build itself (GitVersionValueSource runs
+ # `git rev-list --count HEAD` when the VERSION_CODE env var is unset), which
+ # is why every Gradle job below checks out full β blob-less β history.
# ββ Lint & Static Analysis ββββββββββββββββββββββββββββββββββββββββββ
lint-check:
@@ -95,10 +70,7 @@ jobs:
permissions:
contents: read
timeout-minutes: 30
- needs: setup
if: inputs.run_lint == true
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
steps:
- name: Checkout code
@@ -112,7 +84,7 @@ jobs:
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- cache_read_only: ${{ needs.setup.outputs.cache_read_only }}
+ cache_read_only: ${{ env.GRADLE_CACHE_READ_ONLY }}
install_jetbrains_jdk: 'true'
- name: Lint, Analysis & KMP Smoke Compile
@@ -124,23 +96,21 @@ jobs:
permissions:
contents: read
timeout-minutes: 20
- needs: setup
if: inputs.run_lint == true
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 0
+ filter: 'blob:none'
submodules: true
- name: Gradle Setup
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- cache_read_only: ${{ needs.setup.outputs.cache_read_only }}
+ cache_read_only: ${{ env.GRADLE_CACHE_READ_ONLY }}
- name: Screenshot Test Validation
run: ./gradlew :screenshot-tests:validateDebugScreenshotTest -Pci=true
@@ -334,19 +304,23 @@ jobs:
echo "π All RB checks passed"
# ββ Sharded Unit Tests ββββββββββββββββββββββββββββββββββββββββββββββ
- # Tests are split into 3 shards that run in parallel:
- # shard-core: core:* KMP module tests (allTests)
- # shard-feature: feature:* KMP module tests (allTests)
- # shard-app: Pure-Android/JVM tests (androidApp, desktopApp, core:barcode, etc.)
+ # Tests are split into 3 shards that run in parallel. Module assignment is
+ # balance-driven (measured test-execution time), not purely semantic: the
+ # three heaviest core modules (:core:database ~135s, :core:service ~119s,
+ # :core:network ~66s) are placed in the lighter shards so all three finish
+ # in roughly the same wall time. shard-app already compiles the full
+ # dependency graph (androidApp depends on everything), so hosting extra
+ # core-module tests there costs only their execution time.
+ # shard-core: remaining core:* KMP module tests (allTests)
+ # shard-feature: feature:* KMP module tests + :core:service
+ # shard-app: Pure-Android/JVM tests (androidApp, desktopApp,
+ # core:barcode) + :core:database + :core:network
test-shards:
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 45
- needs: setup
if: inputs.run_unit_tests == true
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
strategy:
fail-fast: false
matrix:
@@ -356,14 +330,11 @@ jobs:
:core:ble:allTests
:core:common:allTests
:core:data:allTests
- :core:database:allTests
:core:domain:allTests
:core:model:allTests
:core:navigation:allTests
- :core:network:allTests
:core:prefs:allTests
:core:repository:allTests
- :core:service:allTests
:core:takserver:allTests
:core:testing:allTests
:core:ui:allTests
@@ -371,19 +342,17 @@ jobs:
:core:ble:koverXmlReport
:core:common:koverXmlReport
:core:data:koverXmlReport
- :core:database:koverXmlReport
:core:domain:koverXmlReport
:core:model:koverXmlReport
:core:navigation:koverXmlReport
- :core:network:koverXmlReport
:core:prefs:koverXmlReport
:core:repository:koverXmlReport
- :core:service:koverXmlReport
:core:takserver:koverXmlReport
:core:testing:koverXmlReport
:core:ui:koverXmlReport
- name: shard-feature
tasks: >-
+ :core:service:allTests
:feature:connections:allTests
:feature:firmware:allTests
:feature:intro:allTests
@@ -392,6 +361,7 @@ jobs:
:feature:node:allTests
:feature:settings:allTests
kover: >-
+ :core:service:koverXmlReport
:feature:connections:koverXmlReport
:feature:firmware:koverXmlReport
:feature:intro:koverXmlReport
@@ -406,25 +376,30 @@ jobs:
:desktopApp:test
:core:barcode:testFdroidDebugUnitTest
:core:barcode:testGoogleDebugUnitTest
+ :core:database:allTests
+ :core:network:allTests
kover: >-
:androidApp:koverXmlReportFdroidDebug
:androidApp:koverXmlReportGoogleDebug
:core:barcode:koverXmlReportFdroidDebug
:core:barcode:koverXmlReportGoogleDebug
:desktopApp:koverXmlReport
+ :core:database:koverXmlReport
+ :core:network:koverXmlReport
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 0
+ filter: 'blob:none'
submodules: true
- name: Gradle Setup
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- cache_read_only: ${{ needs.setup.outputs.cache_read_only }}
+ cache_read_only: ${{ env.GRADLE_CACHE_READ_ONLY }}
- name: Run Tests & Coverage (${{ matrix.shard.name }})
run: |
@@ -471,22 +446,21 @@ jobs:
permissions:
contents: read
timeout-minutes: 60
- needs: setup
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
+ if: inputs.run_android_build == true
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 0
+ filter: 'blob:none'
submodules: true
- name: Gradle Setup
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- cache_read_only: ${{ needs.setup.outputs.cache_read_only }}
+ cache_read_only: ${{ env.GRADLE_CACHE_READ_ONLY }}
- name: Tag main-branch builds as -SNAPSHOT
if: github.event_name == 'push'
@@ -521,26 +495,24 @@ jobs:
permissions:
contents: read
timeout-minutes: 60
- needs: setup
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 0
+ filter: 'blob:none'
submodules: true
- name: Gradle Setup
uses: ./.github/actions/gradle-setup
with:
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- cache_read_only: ${{ needs.setup.outputs.cache_read_only }}
+ cache_read_only: ${{ env.GRADLE_CACHE_READ_ONLY }}
install_jetbrains_jdk: 'true'
- name: Build Desktop
@@ -565,19 +537,17 @@ jobs:
permissions:
contents: read
timeout-minutes: 60
- needs: setup
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
- env:
- VERSION_CODE: ${{ needs.setup.outputs.version_code }}
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 0
+ filter: 'blob:none'
submodules: true
- name: Gradle Setup
Served by rngit 1.5.2 - Generated in 0.1s